﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GraphicsFramework.Entity
{
    public class Line2F
    {
        private Point2F point1;
        private Point2F point2;

        public Line2F()
        {
            point1 = new Point2F();
            point2 = new Point2F();
        }

        public Line2F(float x1, float y1, float x2, float y2)
        {
            point1 = new Point2F(x1, y1);
            point2 = new Point2F(x2, y2);
        }

        public Line2F(Point2F point1, Point2F point2)
        {
            this.point1 = point1;
            this.point2 = point2;
        }

        public Line2F Clone()
        {
            return new Line2F(point1.Clone(), point2.Clone());
        }

        public Point2F GetIntersection(Point2F point, float tolerance)
        {
            if (IsHorizontal())
            {
                if (InBetweenX(point.X))
                {
                    if (Point2F.WithinTolerance(point1.Y, point.Y, tolerance))
                        return new Point2F(point.X, point1.Y);
                }
            }
            else if (IsVertical())
            {
                if (InBetweenY(point.Y))
                {
                    if (Point2F.WithinTolerance(point1.X, point.X, tolerance))
                        return new Point2F(point1.X, point.Y);
                }
            }
            else
            {
                if (InBetweenX(point.X) && InBetweenY(point.Y))
                {
                    float x = point1.X - point2.X;
                    float y = point1.Y - point2.Y;
                    float ratio = y / (float)x;

                    if (Math.Abs(x) > Math.Abs(y))
                    {
                        float compy = point1.Y - (point1.X - point.X) * ratio;
                        tolerance = (float)Math.Abs(Math.Sqrt(x * x + y * y) * tolerance / (float)x);
                        if (Point2F.WithinTolerance(compy, point.Y, tolerance))
                            return new Point2F(point.X, compy);
                    }
                    else
                    {
                        float compx = point1.X - (point1.Y - point.Y) / ratio;
                        tolerance = (float)Math.Abs(Math.Sqrt(x * x + y * y) * tolerance / (float)y);
                        if (Point2F.WithinTolerance(compx, point.X, tolerance))
                            return new Point2F(compx, point.Y);
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Calculate the intersection point of two lines.
        /// </summary>
        /// <param name="line">Line</param>
        /// <returns>Returns the intersection point of two line in Point2F.</returns>
        public Point2F GetIntersection(Line2F line)
        {
            float slope1 = GetSlope();
            float b1 = GetIntercept(slope1);

            float slope2 = line.GetSlope();
            float b2 = line.GetIntercept(slope2);

            Point2F intersection;

            float x;
            float y;

            if (float.IsNaN(slope1))
            {
                if (float.IsNaN(slope2))
                {
                    return null;
                }

                y = slope2 * point1.X + b2;
                intersection =  new Point2F(point1.X, y);
            }
            else if (float.IsNaN(slope2))
            {
                y = slope1 * line.Point2.X + b1;
                intersection = new Point2F(line.Point2.X, y);
            }
            else
            {
                x = (b2 - b1) / (slope1 - slope2);
                y = slope1 * x + b1;
                intersection = new Point2F(x, y);
            }

            if (GetIntersection(intersection, 0.1F) != null)
                return intersection;

            return null;
        }

        /// <summary>
        /// Calculate the intercept of the line. 
        /// From the equation y = mx + b, where b is the intercept.
        /// </summary>
        /// <param name="slope">Slope of the line.</param>
        /// <returns>Return a float value.</returns>
        public float GetIntercept(float slope)
        {
            return point1.Y - slope * point1.X;
        }

        /// <summary>
        /// Calculate the slope of the line.
        /// From the equation y = mx + b, where m is the slope.
        /// </summary>
        /// <returns>Returns a float value if line is not vertical. Otherwise, it will return NaN.</returns>
        public float GetSlope()
        {
            float x = point1.X - point2.X;
            float y = point1.Y - point2.Y;

            if (x == 0)
                return float.NaN;

            return y / x;
        }

        public float GetLength()
        {
            return (float)Math.Sqrt(Math.Pow(point1.X - point2.X, 2) + Math.Pow(point1.Y - point2.Y, 2));
        }

        public bool InBetweenX(float x)
        {
            if (point1.X < point2.X)
            {
                if ((point1.X < x) && (point2.X > x)) return true;
            }
            else
            {
                if ((point1.X > x) && (point2.X < x)) return true;
            }

            return false;
        }

        public bool InBetweenY(float y)
        {
            if (point1.Y < point2.Y)
            {
                if ((point1.Y < y) && (point2.Y > y)) return true;
            }
            else
            {
                if ((point1.Y > y) && (point2.Y < y)) return true;
            }

            return false;
        }

        public bool IsHorizontal()
        {
            if (point1.Y == point2.Y) return true;
            return false;
        }

        public bool IsVertical()
        {
            if (point1.X == point2.X) return true;
            return false;
        }

        public Point2F Point1
        {
            get { return point1; }
            set { point1 = value; }
        }

        public Point2F Point2
        {
            get { return point2; }
            set { point2 = value; }
        }
    }
}
